package security;

import java.io.IOException;
import java.security.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * Class used to send and receive Messages which are AES encoded and decoded over a TCP-Channel
 * @author Group 95
 */
public class AESChannel implements Channel
{
    private static final Logger logger = Logger.getLogger("AESChannel");
    private Channel channel;
    private Cipher cipherReceive;
    private Cipher cipherSend;
    
    
    {
        logger.setLevel(Level.ALL);
    }    
    public AESChannel(Channel channel, String secretKey, String initVector) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException
    {
        logger.log(Level.INFO, "AESChannel channel: {0}, secretKey: {1}, initVector: {2}", new Object[]{channel,secretKey, initVector});        
        this.channel=channel;
        Security.addProvider(new BouncyCastleProvider());
        Key key=new SecretKeySpec(Base64Coder.decodeByte(secretKey),"AES");
        IvParameterSpec initV=new IvParameterSpec(Base64Coder.decodeByte(initVector));
        cipherReceive=Cipher.getInstance("AES/CTR/NoPadding","BC");
        cipherReceive.init(Cipher.DECRYPT_MODE,key,initV);
        cipherSend=Cipher.getInstance("AES/CTR/NoPadding","BC");
        cipherSend.init(Cipher.ENCRYPT_MODE,key,initV);
    }
    
    public AESChannel(Channel channel, SecretKey secretKey, byte[] initVector) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException
    {
        logger.log(Level.INFO, "RSAChannel channel: {0}, secretKey: {1}, initVector: {2}", new Object[]{channel,secretKey, initVector});        
        this.channel=channel;
        Security.addProvider(new BouncyCastleProvider());
        Key key=new SecretKeySpec(secretKey.getEncoded(),"AES");
        IvParameterSpec initV=new IvParameterSpec(initVector);
        cipherReceive=Cipher.getInstance("AES/CTR/NoPadding","BC");
        cipherReceive.init(Cipher.DECRYPT_MODE,key,initV);
        cipherSend=Cipher.getInstance("AES/CTR/NoPadding","BC");
        cipherReceive.init(Cipher.ENCRYPT_MODE,key,initV);
    }    
    
    @Override
    public byte[] receiveMessage() throws IOException {
        logger.log(Level.INFO, "ASEChannel call receivemessage");
        byte[] bRet=decodeMessage(channel.receiveMessage());
        logger.log(Level.INFO, "receiveMessage() received: {0}", new Object[]{bRet});        
        return bRet;
    }
    
    @Override
    public byte[] reReceiveMessage() {
        return decodeMessage(channel.reReceiveMessage());
    }       
    
    private byte[] decodeMessage(byte[] message){
        byte[] bRet=null;
        logger.log(Level.INFO, "receiveMessage() received: {0}", new Object[]{message});
        if(message!=null)
        {
            try 
            {
                message=cipherReceive.doFinal(message);
                logger.log(Level.INFO, "decrypted: {0}", new Object[]{message});
                bRet=new byte[message.length];
                System.arraycopy(message, 0, bRet, 0, message.length);

            }
            catch (IllegalBlockSizeException ex) {
                logger.log(Level.SEVERE, null, ex);
            } catch (BadPaddingException ex) {
                logger.log(Level.SEVERE, null, ex);
            }
        }
        return bRet;
    }    
    
    

    @Override
    public void sendMessage(byte[] message) throws IOException 
    {
        logger.log(Level.INFO, "sendMessage strMessage: {0}", new Object[]{message});
        try 
        {
            logger.log(Level.INFO, "cipher state: {0}", new Object[]{cipherSend});
            message=cipherSend.doFinal(message);
            logger.log(Level.INFO, "encrypted: {0}", new Object[]{message});
            channel.sendMessage(message);

        }
        catch (IllegalBlockSizeException ex) {
            logger.log(Level.SEVERE, null, ex);
        } catch (BadPaddingException ex) {
            logger.log(Level.SEVERE, null, ex);
        }

    }

    @Override
    public void close() 
    {
        channel.close();
    }
    
    /**
     * Checks if something is readable from the underlying socket
     * @return true if something can be read from the socket
     */
    public boolean receivable() throws IOException
    {
        return this.channel.receivable();
    }
}
